{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bade6fb5",
   "metadata": {},
   "source": [
    "### 代码实现"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d17e5ec6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入必要的库\n",
    "import torch\n",
    "import torch.nn as nn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7e990bad",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# 导入 torchvision.models 中的 resnet18 和 ResNet18_Weights\n",
    "from torchvision.models import resnet18, ResNet18_Weights\n",
    "\n",
    "# 使用 resnet18 创建一个模型，并指定权重为默认权重\n",
    "# 其中默认权重对应 ImageNet 数据集预训练模型的权重\n",
    "model = resnet18(weights = ResNet18_Weights.DEFAULT)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8a571070",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 遍历模型的所有参数，并将其 requires_grad 属性设置为 False\n",
    "# 冻结模型参数，使它们在训练过程中不会被更新\n",
    "for parameter in model.parameters():\n",
    "    parameter.requires_grad = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "13c0c211",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ResNet(\n",
       "  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n",
       "  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  (relu): ReLU(inplace=True)\n",
       "  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)\n",
       "  (layer1): Sequential(\n",
       "    (0): BasicBlock(\n",
       "      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (relu): ReLU(inplace=True)\n",
       "      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "    (1): BasicBlock(\n",
       "      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (relu): ReLU(inplace=True)\n",
       "      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (layer2): Sequential(\n",
       "    (0): BasicBlock(\n",
       "      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
       "      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (relu): ReLU(inplace=True)\n",
       "      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (downsample): Sequential(\n",
       "        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
       "        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      )\n",
       "    )\n",
       "    (1): BasicBlock(\n",
       "      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (relu): ReLU(inplace=True)\n",
       "      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (layer3): Sequential(\n",
       "    (0): BasicBlock(\n",
       "      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
       "      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (relu): ReLU(inplace=True)\n",
       "      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (downsample): Sequential(\n",
       "        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
       "        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      )\n",
       "    )\n",
       "    (1): BasicBlock(\n",
       "      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (relu): ReLU(inplace=True)\n",
       "      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (layer4): Sequential(\n",
       "    (0): BasicBlock(\n",
       "      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
       "      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (relu): ReLU(inplace=True)\n",
       "      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (downsample): Sequential(\n",
       "        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
       "        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      )\n",
       "    )\n",
       "    (1): BasicBlock(\n",
       "      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "      (relu): ReLU(inplace=True)\n",
       "      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    )\n",
       "  )\n",
       "  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n",
       "  (fc): Linear(in_features=512, out_features=1000, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 打印模型结构\n",
    "model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "1aa471d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用 nn.Linear 创建一个全连接层，输入特征维度为 512，输出特征维度为 102 进行替换\n",
    "model.fc = nn.Linear(in_features=512, out_features=102, bias=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "41957a34",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conv1.weight False\n",
      "bn1.weight False\n",
      "bn1.bias False\n",
      "layer1.0.conv1.weight False\n",
      "layer1.0.bn1.weight False\n",
      "layer1.0.bn1.bias False\n",
      "layer1.0.conv2.weight False\n",
      "layer1.0.bn2.weight False\n",
      "layer1.0.bn2.bias False\n",
      "layer1.1.conv1.weight False\n",
      "layer1.1.bn1.weight False\n",
      "layer1.1.bn1.bias False\n",
      "layer1.1.conv2.weight False\n",
      "layer1.1.bn2.weight False\n",
      "layer1.1.bn2.bias False\n",
      "layer2.0.conv1.weight False\n",
      "layer2.0.bn1.weight False\n",
      "layer2.0.bn1.bias False\n",
      "layer2.0.conv2.weight False\n",
      "layer2.0.bn2.weight False\n",
      "layer2.0.bn2.bias False\n",
      "layer2.0.downsample.0.weight False\n",
      "layer2.0.downsample.1.weight False\n",
      "layer2.0.downsample.1.bias False\n",
      "layer2.1.conv1.weight False\n",
      "layer2.1.bn1.weight False\n",
      "layer2.1.bn1.bias False\n",
      "layer2.1.conv2.weight False\n",
      "layer2.1.bn2.weight False\n",
      "layer2.1.bn2.bias False\n",
      "layer3.0.conv1.weight False\n",
      "layer3.0.bn1.weight False\n",
      "layer3.0.bn1.bias False\n",
      "layer3.0.conv2.weight False\n",
      "layer3.0.bn2.weight False\n",
      "layer3.0.bn2.bias False\n",
      "layer3.0.downsample.0.weight False\n",
      "layer3.0.downsample.1.weight False\n",
      "layer3.0.downsample.1.bias False\n",
      "layer3.1.conv1.weight False\n",
      "layer3.1.bn1.weight False\n",
      "layer3.1.bn1.bias False\n",
      "layer3.1.conv2.weight False\n",
      "layer3.1.bn2.weight False\n",
      "layer3.1.bn2.bias False\n",
      "layer4.0.conv1.weight False\n",
      "layer4.0.bn1.weight False\n",
      "layer4.0.bn1.bias False\n",
      "layer4.0.conv2.weight False\n",
      "layer4.0.bn2.weight False\n",
      "layer4.0.bn2.bias False\n",
      "layer4.0.downsample.0.weight False\n",
      "layer4.0.downsample.1.weight False\n",
      "layer4.0.downsample.1.bias False\n",
      "layer4.1.conv1.weight False\n",
      "layer4.1.bn1.weight False\n",
      "layer4.1.bn1.bias False\n",
      "layer4.1.conv2.weight False\n",
      "layer4.1.bn2.weight False\n",
      "layer4.1.bn2.bias False\n",
      "fc.weight True\n",
      "fc.bias True\n"
     ]
    }
   ],
   "source": [
    "# 遍历模型的所有参数，并打印出它们的名称和 requires_grad 属性\n",
    "for name, param in model.named_parameters():\n",
    "    print(name, param.requires_grad)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5190c45e",
   "metadata": {},
   "source": [
    "### 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "bec77b2f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 0 Loss: 2.5978439893210212 Acc: 0.15098039215686274\n",
      "Epoch: 10 Loss: 2.0516573743560977 Acc: 0.7274509803921568\n",
      "Epoch: 20 Loss: 1.8921139150187416 Acc: 0.8245098039215686\n",
      "100%|██████████| 30/30 [09:39<00:00, 19.33s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "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SXOO75DevFMq/0oKj5fueF1KHR/oqgAA6NYIJWf7fIW06ntSbbWUMMwcYROTFuiqAADo9gglDQxD2vhzacNi8/Gl10uz/yS5IgNbFwAAPQShRJJqqqR3HjJbSSTzonpf/5lkDwpsXQAA9CCEkrKvpDfvknK3S/Zg6abfMGU8AAAB0LNDSf4ec4RN6XEpNFa67S9S+lWBrgoAgB6p54YSr1da+YAZSHoNku56S+p1SaCrAgCgx+q5s4DZ7dI3X5KG3CTdt5ZAAgBAgPXclhJJShgq3fFaoKsAAADqyS0lAADAUgglAADAEgglAADAEgglAADAEgglAADAEgglAADAEgglAADAEgglAADAEgglAADAEgglAADAEgglAADAEgglAADAEgglAADAErrEVYINw5AklZaWBrgSAABwoRp+txt+x8+nS4SSsrIySVJqamqAKwEAAK1VVlam6Ojo825nMy40vgSQ1+vViRMnFBkZKZvN1m7vW1paqtTUVOXk5CgqKqrd3re7Y7+1Dfutbdhvrcc+axv2W9uca78ZhqGysjKlpKTIbj9/j5Eu0VJit9vVt2/fDnv/qKgoDsA2YL+1Dfutbdhvrcc+axv2W9u0tN8upIWkAR1dAQCAJRBKAACAJfToUOJyufTUU0/J5XIFupQuhf3WNuy3tmG/tR77rG3Yb23TnvutS3R0BQAA3V+PbikBAADWQSgBAACWQCgBAACWQCgBAACW0KNDyXPPPaf09HSFhIRo7Nix+vDDDwNdkqX95Cc/kc1m81uSkpICXZblbNq0STNnzlRKSopsNptWrVrl97xhGPrJT36ilJQUhYaGaurUqfriiy8CU6xFnG+fzZ07t8mxN2HChMAUaxGLFy/WuHHjFBkZqYSEBM2aNUsHDhzw24ZjrakL2W8cb009//zzGjlypG+CtIkTJ+of//iH7/n2OtZ6bCh56623NH/+fP3whz9UZmamrrrqKs2YMUPZ2dmBLs3Shg0bpry8PN+yZ8+eQJdkORUVFRo1apSeffbZZp//xS9+oV//+td69tlntW3bNiUlJenrX/+67xpPPdH59pkkXX/99X7H3nvvvdeJFVrPxo0b9dBDD2nr1q1au3atamtrNX36dFVUVPi24Vhr6kL2m8Txdra+ffvqmWee0fbt27V9+3Zde+21uvnmm33Bo92ONaOHGj9+vDFv3jy/dUOGDDH+8z//M0AVWd9TTz1ljBo1KtBldCmSjLffftv32Ov1GklJScYzzzzjW1ddXW1ER0cbL7zwQgAqtJ6z95lhGMacOXOMm2++OSD1dBUFBQWGJGPjxo2GYXCsXaiz95thcLxdqNjYWONPf/pTux5rPbKlxOPxaMeOHZo+fbrf+unTp2vz5s0BqqprOHjwoFJSUpSenq477rhDR44cCXRJXUpWVpby8/P9jj2Xy6Wrr76aY+88NmzYoISEBF166aW6//77VVBQEOiSLKWkpESSFBcXJ4lj7UKdvd8acLy1rK6uTm+++aYqKio0ceLEdj3WemQoOXXqlOrq6pSYmOi3PjExUfn5+QGqyvquuOIKLVu2TP/617/04osvKj8/X5MmTVJhYWGgS+syGo4vjr3WmTFjhl577TWtW7dOv/rVr7Rt2zZde+21crvdgS7NEgzD0IIFCzR58mQNHz5cEsfahWhuv0kcby3Zs2ePIiIi5HK5NG/ePL399tu67LLL2vVY6xJXCe4oNpvN77FhGE3WodGMGTN890eMGKGJEyfqkksu0Z///GctWLAggJV1PRx7rXP77bf77g8fPlwZGRnq16+f1qxZo1tvvTWAlVnDww8/rM8++0wfffRRk+c41lrW0n7jeGve4MGDtWvXLhUXF2vFihWaM2eONm7c6Hu+PY61HtlS0rt3bwUFBTVJcAUFBU2SHloWHh6uESNG6ODBg4EupctoGK3EsXdxkpOT1a9fP449SY888ohWr16t9evXq2/fvr71HGvn1tJ+aw7Hm8npdGrgwIHKyMjQ4sWLNWrUKP32t79t12OtR4YSp9OpsWPHau3atX7r165dq0mTJgWoqq7H7XZr3759Sk5ODnQpXUZ6erqSkpL8jj2Px6ONGzdy7LVCYWGhcnJyevSxZxiGHn74Ya1cuVLr1q1Tenq63/Mca807335rDsdb8wzDkNvtbt9jrZ064XY5b775puFwOIyXXnrJ2Lt3rzF//nwjPDzcOHr0aKBLs6zHH3/c2LBhg3HkyBFj69atxk033WRERkayz85SVlZmZGZmGpmZmYYk49e//rWRmZlpHDt2zDAMw3jmmWeM6OhoY+XKlcaePXuMO++800hOTjZKS0sDXHngnGuflZWVGY8//rixefNmIysry1i/fr0xceJEo0+fPj16nz344INGdHS0sWHDBiMvL8+3VFZW+rbhWGvqfPuN4615ixYtMjZt2mRkZWUZn332mfGDH/zAsNvtxvvvv28YRvsdaz02lBiGYfzhD38w+vXrZzidTmPMmDF+Q8LQ1O23324kJycbDofDSElJMW699Vbjiy++CHRZlrN+/XpDUpNlzpw5hmGYQzWfeuopIykpyXC5XMaUKVOMPXv2BLboADvXPqusrDSmT59uxMfHGw6Hw0hLSzPmzJljZGdnB7rsgGpuf0kyXnnlFd82HGtNnW+/cbw179577/X9XsbHxxvTpk3zBRLDaL9jzWYYhtHGlhsAAIB20yP7lAAAAOshlAAAAEsglAAAAEsglAAAAEsglAAAAEsglAAAAEsglAAAAEsglAAAAEsglAAAAEsglAAAAEsglAAAAEsglAAAAEv4/wEZCew7Mmp3PgAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 0.8323529411764706\n"
     ]
    }
   ],
   "source": [
    "# 导入必要的库\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets, transforms, models\n",
    "from tqdm import *\n",
    "import numpy as np\n",
    "import sys\n",
    "\n",
    "# 设备检测，若未检测到cuda设备则在CPU上运行\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "# 设置随机种子\n",
    "torch.manual_seed(0)\n",
    "\n",
    "# 定义模型、优化器、损失函数\n",
    "model = model.to(device)\n",
    "optimizer = optim.SGD(model.parameters(), lr=0.002, momentum=0.9)\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "\n",
    "# 设置训练集的数据变换，进行数据增强\n",
    "trainform_train = transforms.Compose([\n",
    "    transforms.RandomRotation(30), # 随机旋转 -30度到30度之间\n",
    "    transforms.RandomResizedCrop((224, 224)), # 随机比例裁剪并进行resize\n",
    "    transforms.RandomHorizontalFlip(p = 0.5), # 随机水平翻转\n",
    "    transforms.RandomVerticalFlip(p = 0.5), # 随机垂直翻转\n",
    "    transforms.ToTensor(),  # 将数据转换为张量\n",
    "    # 对三通道数据进行归一化(均值，标准差)，数值是从ImageNet数据集上的百万张图片中随机抽样计算得到\n",
    "    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n",
    "])\n",
    "\n",
    "# 设置测试集的数据变换，不进行数据增强，仅使用resize和归一化\n",
    "transform_test = transforms.Compose([\n",
    "    transforms.Resize((224, 224)),  # resize\n",
    "    transforms.ToTensor(),  # 将数据转换为张量\n",
    "    # 对三通道数据进行归一化(均值，标准差)，数值是从ImageNet数据集上的百万张图片中随机抽样计算得到\n",
    "    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n",
    "])\n",
    "\n",
    "# 加载训练数据，需要特别注意的是Flowers102数据集，test簇的数据量较多些，所以这里使用\"test\"作为训练集\n",
    "train_dataset = datasets.Flowers102(root='../data/flowers102', split=\"test\", download=True, transform=trainform_train)\n",
    "# 实例化训练数据加载器\n",
    "train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)\n",
    "# 加载测试数据，使用\"train\"作为测试集\n",
    "test_dataset = datasets.Flowers102(root='../data/flowers102', split=\"train\", download=True, transform=transform_test)\n",
    "# 实例化测试数据加载器\n",
    "test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4)\n",
    "\n",
    "# 设置epoch数并开始训练\n",
    "num_epochs = 30  # 设置epoch数\n",
    "loss_history = []  # 创建损失历史记录列表\n",
    "acc_history = []   # 创建准确率历史记录列表\n",
    "\n",
    "# tqdm用于显示进度条并评估任务时间开销\n",
    "for epoch in tqdm(range(num_epochs), file=sys.stdout):\n",
    "    # 记录损失和预测正确数\n",
    "    total_loss = 0\n",
    "    total_correct = 0\n",
    "    \n",
    "    # 批量训练\n",
    "    model.train()\n",
    "    for inputs, labels in train_loader:\n",
    "        # 将数据转移到指定计算资源设备上\n",
    "        inputs = inputs.to(device)\n",
    "        labels = labels.to(device)\n",
    "        \n",
    "        # 预测、损失函数、反向传播\n",
    "        optimizer.zero_grad()\n",
    "        outputs = model(inputs)\n",
    "        loss = criterion(outputs, labels)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        # 记录训练集loss\n",
    "        total_loss += loss.item()\n",
    "    \n",
    "    # 测试模型，不计算梯度\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        for inputs, labels in test_loader:\n",
    "            # 将数据转移到指定计算资源设备上\n",
    "            inputs = inputs.to(device)\n",
    "            labels = labels.to(device)\n",
    "            \n",
    "            # 预测\n",
    "            outputs = model(inputs)\n",
    "            # 记录测试集预测正确数\n",
    "            total_correct += (outputs.argmax(1) == labels).sum().item()\n",
    "        \n",
    "    # 记录训练集损失和测试集准确率\n",
    "    loss_history.append(np.log10(total_loss))  # 将损失加入损失历史记录列表，由于数值有时较大，这里取对数\n",
    "    acc_history.append(total_correct / len(test_dataset))# 将准确率加入准确率历史记录列表\n",
    "    \n",
    "    # 打印中间值\n",
    "    if epoch % 10 == 0:\n",
    "        tqdm.write(\"Epoch: {0} Loss: {1} Acc: {2}\".format(epoch, loss_history[-1], acc_history[-1]))\n",
    "\n",
    "# 使用Matplotlib绘制损失和准确率的曲线图\n",
    "import matplotlib.pyplot as plt\n",
    "plt.plot(loss_history, label='loss')\n",
    "plt.plot(acc_history, label='accuracy')\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "# 输出准确率\n",
    "print(\"Accuracy:\", acc_history[-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc8235b7",
   "metadata": {},
   "source": [
    "### VIT"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "6b08100a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 从torchvision.models中导入ViT_B_16模型和对应权重\n",
    "from torchvision.models import vit_b_16, ViT_B_16_Weights\n",
    "\n",
    "# 使用 vit_b_16 创建一个模型，并指定权重为默认权重\n",
    "model = vit_b_16(weights = ViT_B_16_Weights.DEFAULT)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "83896837",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 遍历模型的所有参数，并将其 requires_grad 属性设置为 False\n",
    "# 冻结模型参数，使它们在训练过程中不会被更新\n",
    "for parameter in model.parameters():\n",
    "    parameter.requires_grad = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0169607e",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "VisionTransformer(\n",
       "  (conv_proj): Conv2d(3, 768, kernel_size=(16, 16), stride=(16, 16))\n",
       "  (encoder): Encoder(\n",
       "    (dropout): Dropout(p=0.0, inplace=False)\n",
       "    (layers): Sequential(\n",
       "      (encoder_layer_0): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_1): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_2): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_3): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_4): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_5): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_6): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_7): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_8): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_9): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_10): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "      (encoder_layer_11): EncoderBlock(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (self_attention): MultiheadAttention(\n",
       "          (out_proj): NonDynamicallyQuantizableLinear(in_features=768, out_features=768, bias=True)\n",
       "        )\n",
       "        (dropout): Dropout(p=0.0, inplace=False)\n",
       "        (ln_2): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "        (mlp): MLPBlock(\n",
       "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
       "          (1): GELU(approximate=none)\n",
       "          (2): Dropout(p=0.0, inplace=False)\n",
       "          (3): Linear(in_features=3072, out_features=768, bias=True)\n",
       "          (4): Dropout(p=0.0, inplace=False)\n",
       "        )\n",
       "      )\n",
       "    )\n",
       "    (ln): LayerNorm((768,), eps=1e-06, elementwise_affine=True)\n",
       "  )\n",
       "  (heads): Sequential(\n",
       "    (head): Linear(in_features=768, out_features=1000, bias=True)\n",
       "  )\n",
       ")"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 打印模型结构\n",
    "model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "bf1aca6a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用 nn.Linear 创建一个全连接层，输入特征维度为 768，输出特征维度为 102 进行替换\n",
    "model.heads = nn.Linear(in_features=768, out_features=102, bias=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "9746a04e",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "class_token False\n",
      "conv_proj.weight False\n",
      "conv_proj.bias False\n",
      "encoder.pos_embedding False\n",
      "encoder.layers.encoder_layer_0.ln_1.weight False\n",
      "encoder.layers.encoder_layer_0.ln_1.bias False\n",
      "encoder.layers.encoder_layer_0.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_0.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_0.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_0.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_0.ln_2.weight False\n",
      "encoder.layers.encoder_layer_0.ln_2.bias False\n",
      "encoder.layers.encoder_layer_0.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_0.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_0.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_0.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_1.ln_1.weight False\n",
      "encoder.layers.encoder_layer_1.ln_1.bias False\n",
      "encoder.layers.encoder_layer_1.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_1.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_1.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_1.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_1.ln_2.weight False\n",
      "encoder.layers.encoder_layer_1.ln_2.bias False\n",
      "encoder.layers.encoder_layer_1.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_1.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_1.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_1.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_2.ln_1.weight False\n",
      "encoder.layers.encoder_layer_2.ln_1.bias False\n",
      "encoder.layers.encoder_layer_2.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_2.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_2.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_2.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_2.ln_2.weight False\n",
      "encoder.layers.encoder_layer_2.ln_2.bias False\n",
      "encoder.layers.encoder_layer_2.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_2.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_2.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_2.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_3.ln_1.weight False\n",
      "encoder.layers.encoder_layer_3.ln_1.bias False\n",
      "encoder.layers.encoder_layer_3.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_3.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_3.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_3.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_3.ln_2.weight False\n",
      "encoder.layers.encoder_layer_3.ln_2.bias False\n",
      "encoder.layers.encoder_layer_3.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_3.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_3.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_3.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_4.ln_1.weight False\n",
      "encoder.layers.encoder_layer_4.ln_1.bias False\n",
      "encoder.layers.encoder_layer_4.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_4.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_4.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_4.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_4.ln_2.weight False\n",
      "encoder.layers.encoder_layer_4.ln_2.bias False\n",
      "encoder.layers.encoder_layer_4.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_4.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_4.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_4.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_5.ln_1.weight False\n",
      "encoder.layers.encoder_layer_5.ln_1.bias False\n",
      "encoder.layers.encoder_layer_5.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_5.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_5.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_5.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_5.ln_2.weight False\n",
      "encoder.layers.encoder_layer_5.ln_2.bias False\n",
      "encoder.layers.encoder_layer_5.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_5.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_5.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_5.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_6.ln_1.weight False\n",
      "encoder.layers.encoder_layer_6.ln_1.bias False\n",
      "encoder.layers.encoder_layer_6.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_6.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_6.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_6.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_6.ln_2.weight False\n",
      "encoder.layers.encoder_layer_6.ln_2.bias False\n",
      "encoder.layers.encoder_layer_6.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_6.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_6.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_6.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_7.ln_1.weight False\n",
      "encoder.layers.encoder_layer_7.ln_1.bias False\n",
      "encoder.layers.encoder_layer_7.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_7.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_7.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_7.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_7.ln_2.weight False\n",
      "encoder.layers.encoder_layer_7.ln_2.bias False\n",
      "encoder.layers.encoder_layer_7.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_7.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_7.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_7.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_8.ln_1.weight False\n",
      "encoder.layers.encoder_layer_8.ln_1.bias False\n",
      "encoder.layers.encoder_layer_8.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_8.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_8.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_8.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_8.ln_2.weight False\n",
      "encoder.layers.encoder_layer_8.ln_2.bias False\n",
      "encoder.layers.encoder_layer_8.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_8.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_8.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_8.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_9.ln_1.weight False\n",
      "encoder.layers.encoder_layer_9.ln_1.bias False\n",
      "encoder.layers.encoder_layer_9.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_9.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_9.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_9.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_9.ln_2.weight False\n",
      "encoder.layers.encoder_layer_9.ln_2.bias False\n",
      "encoder.layers.encoder_layer_9.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_9.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_9.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_9.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_10.ln_1.weight False\n",
      "encoder.layers.encoder_layer_10.ln_1.bias False\n",
      "encoder.layers.encoder_layer_10.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_10.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_10.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_10.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_10.ln_2.weight False\n",
      "encoder.layers.encoder_layer_10.ln_2.bias False\n",
      "encoder.layers.encoder_layer_10.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_10.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_10.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_10.mlp.3.bias False\n",
      "encoder.layers.encoder_layer_11.ln_1.weight False\n",
      "encoder.layers.encoder_layer_11.ln_1.bias False\n",
      "encoder.layers.encoder_layer_11.self_attention.in_proj_weight False\n",
      "encoder.layers.encoder_layer_11.self_attention.in_proj_bias False\n",
      "encoder.layers.encoder_layer_11.self_attention.out_proj.weight False\n",
      "encoder.layers.encoder_layer_11.self_attention.out_proj.bias False\n",
      "encoder.layers.encoder_layer_11.ln_2.weight False\n",
      "encoder.layers.encoder_layer_11.ln_2.bias False\n",
      "encoder.layers.encoder_layer_11.mlp.0.weight False\n",
      "encoder.layers.encoder_layer_11.mlp.0.bias False\n",
      "encoder.layers.encoder_layer_11.mlp.3.weight False\n",
      "encoder.layers.encoder_layer_11.mlp.3.bias False\n",
      "encoder.ln.weight False\n",
      "encoder.ln.bias False\n",
      "heads.weight True\n",
      "heads.bias True\n"
     ]
    }
   ],
   "source": [
    "# 遍历模型的所有参数，并打印出它们的名称和 requires_grad 属性\n",
    "for name, param in model.named_parameters():\n",
    "    print(name, param.requires_grad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "532c2de8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 0 Loss: 2.585463130374999 Acc: 0.17647058823529413\n",
      "Epoch: 10 Loss: 2.0090513371043364 Acc: 0.7245098039215686\n",
      "Epoch: 20 Loss: 1.8183318470240557 Acc: 0.8294117647058824\n",
      "100%|██████████| 30/30 [20:51<00:00, 41.72s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 0.85\n"
     ]
    }
   ],
   "source": [
    "# 导入必要的库\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets, transforms, models\n",
    "from tqdm import *\n",
    "import numpy as np\n",
    "import sys\n",
    "\n",
    "# 设备检测，若未检测到cuda设备则在CPU上运行\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "# 设置随机种子\n",
    "torch.manual_seed(0)\n",
    "\n",
    "# 定义模型、优化器、损失函数\n",
    "model = model.to(device)\n",
    "optimizer = optim.SGD(model.parameters(), lr=0.002, momentum=0.9)\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "\n",
    "# 设置训练集的数据变换，进行数据增强\n",
    "trainform_train = transforms.Compose([\n",
    "    transforms.RandomRotation(30), # 随机旋转 -30度到30度之间\n",
    "    transforms.RandomResizedCrop((224, 224)), # 随机比例裁剪并进行resize\n",
    "    transforms.RandomHorizontalFlip(p = 0.5), # 随机水平翻转\n",
    "    transforms.RandomVerticalFlip(p = 0.5), # 随机垂直翻转\n",
    "    transforms.ToTensor(),  # 将数据转换为张量\n",
    "    # 对三通道数据进行归一化(均值，标准差)，数值是从ImageNet数据集上的百万张图片中随机抽样计算得到\n",
    "    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n",
    "])\n",
    "\n",
    "# 设置测试集的数据变换，不进行数据增强，仅使用resize和归一化\n",
    "transform_test = transforms.Compose([\n",
    "    transforms.Resize((224, 224)),  # resize\n",
    "    transforms.ToTensor(),  # 将数据转换为张量\n",
    "    # 对三通道数据进行归一化(均值，标准差)，数值是从ImageNet数据集上的百万张图片中随机抽样计算得到\n",
    "    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n",
    "])\n",
    "\n",
    "# 加载训练数据，需要特别注意的是Flowers102数据集，test簇的数据量较多些，所以这里使用\"test\"作为训练集\n",
    "train_dataset = datasets.Flowers102(root='../data/flowers102', split=\"test\", download=True, transform=trainform_train)\n",
    "# 实例化训练数据加载器\n",
    "train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)\n",
    "# 加载测试数据，使用\"train\"作为测试集\n",
    "test_dataset = datasets.Flowers102(root='../data/flowers102', split=\"train\", download=True, transform=transform_test)\n",
    "# 实例化测试数据加载器\n",
    "test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4)\n",
    "\n",
    "# 设置epoch数并开始训练\n",
    "num_epochs = 30  # 设置epoch数\n",
    "loss_history = []  # 创建损失历史记录列表\n",
    "acc_history = []   # 创建准确率历史记录列表\n",
    "\n",
    "# tqdm用于显示进度条并评估任务时间开销\n",
    "for epoch in tqdm(range(num_epochs), file=sys.stdout):\n",
    "    # 记录损失和预测正确数\n",
    "    total_loss = 0\n",
    "    total_correct = 0\n",
    "    \n",
    "    # 批量训练\n",
    "    model.train()\n",
    "    for inputs, labels in train_loader:\n",
    "        # 将数据转移到指定计算资源设备上\n",
    "        inputs = inputs.to(device)\n",
    "        labels = labels.to(device)\n",
    "        \n",
    "        # 预测、损失函数、反向传播\n",
    "        optimizer.zero_grad()\n",
    "        outputs = model(inputs)\n",
    "        loss = criterion(outputs, labels)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        # 记录训练集loss\n",
    "        total_loss += loss.item()\n",
    "    \n",
    "    # 测试模型，不计算梯度\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        for inputs, labels in test_loader:\n",
    "            # 将数据转移到指定计算资源设备上\n",
    "            inputs = inputs.to(device)\n",
    "            labels = labels.to(device)\n",
    "            \n",
    "            # 预测\n",
    "            outputs = model(inputs)\n",
    "            # 记录测试集预测正确数\n",
    "            total_correct += (outputs.argmax(1) == labels).sum().item()\n",
    "        \n",
    "    # 记录训练集损失和测试集准确率\n",
    "    loss_history.append(np.log10(total_loss))  # 将损失加入损失历史记录列表，由于数值有时较大，这里取对数\n",
    "    acc_history.append(total_correct / len(test_dataset))# 将准确率加入准确率历史记录列表\n",
    "    \n",
    "    # 打印中间值\n",
    "    if epoch % 10 == 0:\n",
    "        tqdm.write(\"Epoch: {0} Loss: {1} Acc: {2}\".format(epoch, loss_history[-1], acc_history[-1]))\n",
    "\n",
    "# 使用Matplotlib绘制损失和准确率的曲线图\n",
    "import matplotlib.pyplot as plt\n",
    "plt.plot(loss_history, label='loss')\n",
    "plt.plot(acc_history, label='accuracy')\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "# 输出准确率\n",
    "print(\"Accuracy:\", acc_history[-1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c3ed3425",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
